﻿using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
using UnityEngine.Profiling;

namespace GameToolkit
{
    public enum ENativeSize
    {
        Bytes = 0,
        KB = 10,
        MB = 20,
        GB = 30,
        TB = 40,
    }

    /// <summary>
    /// Native 数据分配
    /// </summary>
    public interface INativeAllocating
    {
        int InstanceID { get; }
    }

    public interface INative<T> : INativeAllocating
        where T : struct
    {
        T GetNativeData();

    }

    public interface INativePool : System.IDisposable, IEnumerable
    {
        int Length { get; }
        object GetNativeInstance(int index);
    }

    public class NativeInstance<NativeData>
        where NativeData : struct
    {
        private Pool mPool;
        private int mNativeId;
        private INative<NativeData> mInstance;

        public int NativeID { get { return mNativeId; } }
        public bool IsAllocated { get { return mPool != null; } }
        Transform mTrans;

        private NativeInstance(INative<NativeData> inst, Transform trans)
        {
            mInstance = inst;
            mNativeId = -1;
            if (trans != null)
            {
                mTrans = trans;
            }
            else
            {
                var com = inst as Component;
                mTrans = com == null ? null : com.transform;
            }
        }

        public Transform transform
        {
            get { return mTrans; }
            set
            {
                if (mTrans != value)
                {
                    mTrans = value;
                    if (mPool != null)
                        mPool.UpdateTransform(this);
                }
            }
        }

        public INative<NativeData> Instance { get { return mInstance; } }

        public NativeData AccessNativeData()
        {
            return mPool == null ? default : mPool.GetNativeData(mNativeId);
        }

        public void UpdateNativeData()
        {
            if(mPool != null) 
            {
                mPool.UpdateNativeData(this);
            }
        }

        public bool Release()
        {
            if (mPool != null)
            {
                return mPool.RemoveInstance(this);
            }
            mPool = null;
            mNativeId = -1;
            return false;
        }

        public void FlushNativeJobs()
        {
            if (mPool != null)
                mPool.FlushNativeJobs();
        }

        public class Pool : INativePool, IEnumerable<NativeInstance<NativeData>>
        {
            bool mIsValid;
            private int mLength;
            private NativeInstance<NativeData>[] mInstance;
            private NativeArray<NativeData> mNativeArr;
            private TransformAccessArray mTransArr;
            public event System.Action<NativeInstance<NativeData>> OnValidateData;

            public int Length { get { return mLength; } }
            public NativeInstance<NativeData> this[int index] { get { return mInstance[index]; } }
            public object GetNativeInstance(int index) { return mInstance[index]; }
            public NativeData GetNativeData(int index) { return mNativeArr[index]; }
            public NativeArray<NativeData> NativeDatas { get { return mNativeArr; } }
            public int Capacity { get { return mInstance.Length; } }

            bool mHasJob;
            JobHandle mJob;
            readonly object mLock = new object();

            public TransformAccessArray GetTransforms()
            {
                if (!mTransArr.isCreated && mNativeArr.IsCreated)
                {
                    mTransArr = new TransformAccessArray(mNativeArr.Length);
                    for (int i = 0; i < mLength; i++)
                    {
                        mTransArr.Add(mInstance[i].transform);
                    }
                }
                return mTransArr;
            }

            public int IndexOf(INative<NativeData> inst)
            {
                for (int i = 0; i < mLength; i++)
                {
                    if (mInstance[i].mInstance == inst)
                        return i;
                }
                return -1;
            }

            public int IndexOf(int instId)
            {
                for (int i = 0; i < mLength; i++)
                {
                    if (mInstance[i].mInstance.InstanceID == instId)
                        return i;
                }
                return -1;
            }

            public Pool(int capacity)
            {
                capacity = Mathf.Max(16, capacity);
                mInstance = new NativeInstance<NativeData>[capacity];
                mNativeArr = new NativeArray<NativeData>(capacity, Allocator.Persistent);
                mLength = 0;
                mIsValid = true;
            }

            public void SetNativeJob(JobHandle job)
            {
                if (mHasJob)
                    CompleteNativeJob();
                mHasJob = true;
                mJob = job;
            }

            public void FlushNativeJobs()
            {
                if (mHasJob)
                {
                    mHasJob = false;
                    CompleteNativeJob();
                }
            }

            protected virtual void CompleteNativeJob()
            {
                mJob.Complete();
            }

            public virtual double CalculateNativeSize(ENativeSize unit)
            {
                if (mNativeArr.IsCreated)
                {
                    long size = (Marshal.SizeOf<NativeData>() * mNativeArr.Length);
                    return size / (double)(1L << (int)unit);
                }
                return 0;
            }

            private void EnsureSize(int size)
            {
                if (mInstance == null || mInstance.Length < size)
                {
#if UNITY_EDITOR
                    Profiler.BeginSample(string.Format("Malloc Native<{0}> Data.", typeof(NativeData).Name));
#endif
                    size = Mathf.Max(size, 16, Mathf.CeilToInt(mLength * 1.5f));
                    OnMalloc(size);

#if UNITY_EDITOR
                    Profiler.EndSample();
#endif
                }
            }

            protected virtual void OnMalloc(int size)
            {
                var arr = new NativeInstance<NativeData>[size];
                var narr = new NativeArray<NativeData>(size, Allocator.Persistent);
                if (mTransArr.isCreated)
                {
                    mTransArr.capacity = size;
                }
                if (mLength > 0)
                {
                    for (int i = 0; i < mLength; i++)
                    {
                        arr[i] = mInstance[i];
                        narr[i] = mNativeArr[i];
                    }
                }
                mInstance = arr;
                mNativeArr.Dispose();
                mNativeArr = narr;
            }

            public NativeInstance<NativeData> AddInstance(INative<NativeData> inst, Transform trans = null)
            {
                var n = IndexOf(inst);
                if (n != -1)
                    return mInstance[n];
                EnsureSize(mLength + 1);
                var ninst = new NativeInstance<NativeData>(inst, trans);
                ninst.mPool = this;
                ninst.mNativeId = mLength++;
                mInstance[ninst.mNativeId] = ninst;
                if (mTransArr.isCreated)
                    mTransArr.Add(ninst.transform);
                ResetNativeData(ninst.mNativeId, ninst.Instance);
                return ninst;
            }

            public bool RemoveInstance(NativeInstance<NativeData> inst)
            {
                var id = inst.mNativeId;
                if (id >= 0 && id < mLength && inst.mPool == this)
                {
                    --mLength;
                    RemoveAtSwapBack(id);
                    inst.mNativeId = -1;
                    inst.mPool = null;
                    return true;
                }
                return false;
            }

            protected virtual void RemoveAtSwapBack(int index)
            {
                if (index < mLength)
                {
                    mInstance[index] = mInstance[mLength];
                    mNativeArr[index] = mNativeArr[mLength];
                    mInstance[index].mNativeId = index;
                }
                mInstance[mLength] = null;
                if (mTransArr.isCreated)
                    mTransArr.RemoveAtSwapBack(index);
            }

            public void UpdateNativeData(NativeInstance<NativeData> inst)
            {
                if (inst.mNativeId >= 0 && inst.mNativeId < mLength && inst.mPool == this && inst.mInstance != null)
                {
                    ResetNativeData(inst.mNativeId, inst.mInstance);
                    OnValidateData?.Invoke(inst);
                }
            }

            protected virtual void ResetNativeData(int index, INative<NativeData> inst)
            {
                mNativeArr[index] = inst.GetNativeData();
            }

            public void UpdateTransform(NativeInstance<NativeData> inst)
            {
                if (inst.mNativeId >= 0 && inst.mNativeId < mLength && inst.mPool == this && mTransArr.isCreated)
                {
                    mTransArr[inst.mNativeId] = inst.transform;
                }
            }

            public virtual void Clear()
            {
                for (int i = 0; i < mLength; i++)
                {
                    var inst = mInstance[i];
                    inst.mNativeId = -1;
                    inst.mPool = null;
                    mInstance[i] = null;
                }
                if (mTransArr.isCreated)
                {
                    for (int i = mLength - 1; i >= 0; i--)
                    {
                        mTransArr.RemoveAtSwapBack(i);
                    }
                }
                mLength = 0;
            }

            public virtual void Dispose()
            {
                lock (mLock)
                {
                    if (!mIsValid)
                        return;
                    mIsValid = false;
                }
                for (int i = 0; i < mLength; i++)
                {
                    mInstance[i].mNativeId = -1;
                    mInstance[i].mPool = null;
                }
                if (mNativeArr.IsCreated)
                    mNativeArr.Dispose();
                if (mTransArr.isCreated)
                    mTransArr.Dispose();
                mNativeArr = default;
                mTransArr = default;
                mLength = 0;
                OnValidateData = null;
                mInstance = null;
#if UNITY_EDITOR
                Debug.LogFormat("NativePool<{0}> was disposed.", typeof(NativeData).Name);
#endif
            }

            public IEnumerator<NativeInstance<NativeData>> GetEnumerator()
            {
                return new Enumerator(this);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return new Enumerator(this);
            }

            ~Pool()
            {
                Dispose();
            }
        }

        internal struct Enumerator : IEnumerator<NativeInstance<NativeData>>
        {
            Pool mPool;
            int mIterIndex;
            public NativeInstance<NativeData> Current { get { return mPool[mIterIndex]; } }

            object IEnumerator.Current { get { return mPool[mIterIndex]; } }

            public Enumerator(Pool pool)
            {
                mPool = pool;
                mIterIndex = -1;
            }

            public void Dispose()
            {

            }

            public bool MoveNext()
            {
                return ++mIterIndex < mPool.Length;
            }

            public void Reset()
            {
                mIterIndex = -1;
            }

        }

    }
}